
Updated at : Fri Dec 05 2025
AWS Lambda에서 외부 패키지를 사용하려면 어떻게 해야할까? 당연하게도 람다 계층 서비스를 가장 먼저 떠올릴 것이다. 실제로 대부분의 경우 람다 계층을 활용하며, 딱히 제한될 경우가 많지 않다.
하지만 많지 않은 경우가 당연히 있다. 대표적으로 패키지 사이즈 문제가 있다. 람다 계층에 zip파일로 업로드할 때는 50mb가 한계이다. zip 파일을 S3 업로드한 후 생성하는 방법은 좀 더 허용치가 높아지나, 이 역시 최대 250mb까지이다. 게다가 이 한계치는 압축해제시 필요로하는 공간까지 포함한 수치이기에, S3에 업로드된 패키지가 250mb가 안된다하여 안심할 수 있는 수치가 아니다. 즉, 생각보다 그 한계치가 낮다는 것이다.
그럼 만일 그보다 큰 패키지를 사용하고 싶을 때는 어떻게 해야할까. 여러 방법이 있겠지만, 그 중 한가지의 방법을 기록하려 한다. 이는 람다의 임시 메모리(/tmp)를 활용하는 것이다. 활용 방법은 다음과 같다.
(1) 람다 시작 > (2) 압축 패키지 파일 읽어오기 (e.g. S3업로드된 zip파일) > (3) 읽어온 파일을 임시 메모리에 압축해제/저장 > (4) 저장한 소스를 람다가 읽을 수 있도록(= 사용할 수 있도록) 연결 > (5) 일반적인 패키지처럼 import 후 활용
압축되지 않은 소스을 업로드하여 활용할 수 있겠으나.. 대부분의 경우, 더 큰 용량 + 많은 파일에 따른 다수의 읽기 작업은 압축해제보다 더 비싼 비용이 든다. 즉, 압축파일 이용은 필수다.
위의 단계를 거치면, 기본적으로는 활용할 수 없던 대용량 패키지도 람다에서 활용할 수 있다.
그러나 대충 보아도 알 수 있는 이 방법의 치명적인 단점이 있다. 바로 속도 문제다. 대용량 파일을 읽어온 후 압축해제까지 진행해야만 함수가 실행가능하다(대역폭 걱정은 안해도 될 것이다. 내가 알기로 S3와 람다의 리전이 같다면 비용이 발생하지 않는다). 이 문제가 해결되지 못한다면 이 함수를 실전에 사용하는 것에는 좀 무리가 있을 수 있다.
이를 해결할 수 있는 기법이 Warm start 이다. 이는 Lambda 실행 환경의 생명 주기를 활용하는 기법으로, 람다는 첫 시작시 환경을 구성한 뒤, 이를 람다가 살아있는 동안(= 더이상의 호출이 없어 종료되기 전까지) 유지한다는 점을 이용하는 것이다.
람다의 임시 메모리도 환경에 해당된다. 이 공간에 저장된 내용은 람다가 살아있는 동안 유지되기 때문에, 재호출 발생시 기존의 메모리를 그대로 활용할 수 있다. 그리고 앞서 소개한 2 ~ 4번까지의 방법은 모두 환경설정에 관한 동작이다. 즉 Warm start를 활용하면, 4번까지의 동작을 건너뛰고 5번부터 시작하는, 일반적인 람다 함수와 다를바 없는 동작이 가능케 되는 것이다.
그러나 이 방안의 마지막 허들이 남아있으니, 이는 람다의 동시성 문제이다. 람다함수를 동시에 다수 호출을 하게 되는 경우, 최대 제한량 1000개를 초과하지 않는한, 호출한 개수만큼의 람다함수가 병렬 실행된다. 이들은 모두, 개별적인 람다함수로서 환경을 공유하지 않는다. 즉, 함수가 동시에 여럿 실행된다면, Warm start를 위해 함수를 한번 호출하는 행위는 무용한 것이 된다.
이 문제는 람다함수 설정의 함수 동시성을 직접 지정함으로서 해결할 수 있다. 함수 동시성이 예약된 경우, 지정한 개수만큼만 동시실행이 가능해진다. 즉, 동시성 설정과 함께 해당 개수만큼의 람다함수를 모두 미리 호출해 놓는다면, 동시에 실행되는 모든 람다함수들이 Warm start가 되도록 만들 수 있다. 다만, 람다함수의 동시성은 계정 전체가 공유하며 기본 1000개이기 때문에, 무분별한 설정은 계정내 다른 람다 실행에 방해가 될 수 있을테니 명확한 계획이 필요할 것이다.
람다가 각자의 환경을 유지하는 시간은 명확하게 알려져 있지 않다. AWS에서도 공식적으로 안내하지 않는다. 실험적으로 알려진 시간은 5분 ~ 10분 정도인 것으로 알려지는 듯 하며, 내 개인적인 실험으로는 그냥 5분정도로 생각하는게 맞는듯 하다. 따라서 만일, 함수가 언제든 Warm start가 되도록 만들고 싶다면 5분에 한번씩 호출되도록 설정해야할 것이다. 그러나 이 행위가 너무 비싸다/무겁다라고 생각되면, 함수가 실행될 것이라 예측되는 시점에 선제적으로 한번 호출해주는 것도 좋은 방안이지 싶다. 물론 그러려면 좋은 설계가 우선되어야겠지.. ㅎ
이를 학습하게된 계기는 사용하려던 파이썬 패키지의 용량이 너무 커서 해결 방법을 알아보고자 하였던 것이다. 따라서 이때 진행한 방법을 예시로서 기록해 놓도록 하겠다.
일반적으로 외부 패키지를 추가하려 할 경우, 굳이 Docker를 이용할 이유는 없으니 그냥 임의 폴더에서 pip install 하면 될 것이다. 그러나 당시의 내가 굳이 Docker를 활용했던 이유는 다음과 같다.
이와 같은 이유로 도커를 사용하게 되었다.
알다시피 도커란 결국 가상의 환경을 만들어 활용하는 것이다. 도커를 사용하는데는 여러가지 이유가 있겠지만, 가장 우선되는 것은 배포환경과 동일한 환경에서 작업한다는 점일 것이다. 따라서 이번 경우도 람다 파이썬 함수과 동일한 환경을 구성하기 위해 다음의 명령어를 사용하였다.
docker run --rm -v ${PWD}:/host -it --entrypoint "/bin/bash" amazon/aws-lambda-python:3.13
이 명령어의 세부적인 사항은 다음과 같다.
| 명령어 | 내용 |
|---|---|
| docker run | 신규 컨테이너 생성 |
| --rm | 컨테이너가 종료되면 해당 컨테이너를 자동으로 제거 |
| -v ${PWD}:/host | 볼륨 마운트를 설정 ${PWD}: 호스트(사용자 컴퓨터)에서 현재 작업 중인 디렉토리(=명령어 실행위치) /host: 컨테이너 내부에서 ${PWD}와 매핑될 경로(폴더), 원하는 폴더명, 위치로 변경해도 된다 |
| -it | -i (interactive): 컨테이너의 표준 입력(stdin)을 열어두어 상호 작용 -t (tty): 의사 터미널(pseudo-TTY)을 할당하여 사용자에게 친숙한 터미널 환경을 제공 |
| --entrypoint "/bin/bash" | 컨테이너 시작 시 실행될 기본 명령어(ENTRYPOINT)를 재정의 |
| amazon/aws-lambda-python:3.13 | 컨테이너를 생성하는 데 사용될 Docker 이미지(AWS에서 제공하는 공식 이미지) |
이 중 람다환경만을 위한 특이점은 기본 entrypoint 재정의이다. 나의 목적은 람다 환경에 사용될 패키지를 다운받아 설치하는 것이다. 하지만 AWS에서 제공하는 람다이미지는 실행시 바로 작성된 스크립트(함수)가 실행되도록 설정되어 있다. 따라서 함수가 실행되지 않고, 컨테이너 내부에 접근하기 위해서는 entrypoint를 재정의 해야만 한다.
패키지 획득을 위한 특이점은 -v ${PWD}:/host 부분이다. 이 옵션의 추가를 통해, 도커 명령어가 실행된 지점(진입점)이 컨테이너의 /host 폴더와 동기화된다. 따라서 컨테이너의 host 폴더에 pip설치를 하면 로컬에서 확인을, 로컬 파일을 진입점으로 이동시키면 컨테이너에서 확인을 할 수 있게 된다.
이 다음은 일반적인 리눅스 명령어 및 패키지 설치를 진행하면 된다. 다만, 나는 리눅스 및 파이썬에 익숙지 못하니 몇 가지 명령어들을 기록을 겸해 남기겠다.
폴더 구성 및 패키지가 잘 다운로드되었는지 직관적인 확인을 위해, Docker Desktop을 사용하는 것이 좋았다. 이를 이용하는 경우, Contianer 탭에서 현재 실행중인 컨테이너를 선택해 폴더 구성을 UI로 확인가능하다.
| 명령어 | 내용 |
|---|---|
| mkdir {폴더명} | 신규 폴더 생성 |
| cd {폴더명} | 해당 폴더 위치로 이동 PowerShell과 마찬가지로 .. 사용시 상위폴더로 이동 맨앞에 / 를 붙이면 루트 기준에서 이동, 없으면 현재 위치 기준 하위 폴더로 이동 |
| pwd | 현재 위치한 지점 |
| exit | 현재 진행중인 shell 세션 종료 도커는 최초 프로세스가 종료되면 컨테이너가 종료되도록 설계되어있음 셀 종료를 도커가 감지하고 컨테이너가 종료되는 것임 |
| pip install -U --pre {패키지명} -t {폴더명} | pip install: 파이썬 패키지 설치 -U: 이미 설치된 패키지가 있더라도, 지정된 패키지를 최신 버전으로 업그레이드 --pre: 안정 버전뿐만 아니라 Pre-release 버전까지 포함하여 설치를 시도 -t: 패키지를 설치할 폴더 별도 지정 |
| dnf update -y | 전체 시스템 업데이트. 신규 리눅스 서버 접속시의 관습적 행위 서버를 설치한 시점(OS 이미지)과 현재 사이에 여러 변경이 있을 수 있기에 보안/버그수정/종속성문제/안정성 향상 등의 이유로 첫 접속시는 반드시 사용을 권장 |
| dnf install {패키지명} -y | 리눅스 패키지 설치 |
| tar -xf {압축파일명} --strip-components=1 | tar: 유닉스/리눅스의 압축프로그램이라고 생각해도 무방 -x: 추출(extract)를 의미, 압축해제 요청 -f: 다음에 지정된 인수가 사용할 파일의 이름임을 알림, 이 다음에 압축파일명 기재 --strip-components=1: 압축파일내의 가장 상위 디렉토리 1개를 제거 |
| chmod +x {파일명} | chmod: 파일이나 디렉토리의 권한을 변경하는데 사용되는 리눅스/유닉스 명령어 +: 기존 권한에 추가 x: 해당 파일이나 스크립트의 실행권한(+x 사용시, 실행가능하도록 권한 설정됌) |
| cp {파일명} {신규위치} | 파일을 신규 위치에 복사, e.g. cp /usr/bin/파일명 /host/bin/ |
리눅스에서 폴더를 의미하려면 '/'를 마지막에 붙여주어야 한다
E.g. pip install {패키지명} -t folderA/
아직 많은 사람들이 yum에 더 익숙하지 싶다. dnf는 yum의 차세대 패키지 관리 명령어이다. aws-lambda-python:3.13 에서 yum은 사용불가능하다. 앞으로 나올 버전들도 모두 dnf를 써야할 것이다.
위의 내용들을 활용해 패키지를 설치 및 압축해제, 권한부여를 진행 후 그 소스들을 최초 설정한 폴더(host) 내부에 위치시키면, 내 PC에서도 이를 확인할 수 있다. 이를 압축하여 S3에 업로드하면 된다.
이제 S3에 업로드한 파일을 읽어와, 압축해제 및 환경설정 후 사용하면 된다. 이는 예제로 남기겠다.
def initialize_package():
# 웜 스타트: 모듈 폴더가 이미 존재하는지 체크
# <모듈 경로>는 <압축해제 경로>로 해도 좋고, <압축해제 경로> + <압축파일내 패키지의 모듈폴더>를 사용해도 좋고.. 자유다
if Path(<모듈 경로>).exists():
return True
# --- 콜드 스타트: 폴더가 없을때만 초기화 ---
isDecompressed = True
try:
# 1. S3에서 파일 다운로드(boto3 활용)
s3_client.download_file(<s3 버킷이름>, <s3 파일 키>, <다운 받을 경로>)
# 2. 압축 해제 (***zipfile 모듈 사용***)
with zipfile.ZipFile(Path(<다운 받은 경로>), 'r') as zip_ref:
zip_ref.extractall(<압축해제 경로>) # zip_ref.extractall()은 리눅스 실행 권한을 유지합니다.
# 3. ZIP 파일 정리
os.remove(Path(<다운 받은 경로>))
# 4. Python이 파이썬 모듈을 찾도록 경로를 지정하는 명령어
sys.path.append(<모듈 경로>)
# 5. (선택) 실행 권한 부여
# 실행파일이 존재하는 경우
# 압축폴더에 미리 구성해둔 경로사용 필요
# 0o755 === +x
os.chmod(Path(<압축해제 경로>) / <파일의 압축 폴더내 경로>, 0o755)
except Exception as e:
isDecompressed = False
return isDecompressed
def lambda_handler(event, context):
# 환경 초기화
isInitialize = initialize_package()
if not isInitialize:
return
# sys.path가 업데이트되었으므로 이제 import 가능
import {패키지}
# 선택적 추가 1. 실행파일 사용시 path 설정
# (자동 인식되어서 활용되어야 할 경우에만) (사용하는 패키지에 별도로 위치를 안내해 줄 수 있다면 필요없을 수 있음)
os.environ['PATH'] = os.environ['PATH'] + ':' + <tmp 경로>
# 선택적 추가 2. CLI명령어를 이용하는 경우, 매번 모듈 경로 추가가 선행되어야 함
# E.g. "python -c ~~~" 용도의 코드, (module 명령어: python -m ~~~) 대체를 위함
python_command = (
f"import sys; sys.path.append(<모듈 경로>); " # 실행 환경에 Python 패키지 경로 추가
f"import <패키지>; " # sys.path가 업데이트 후 import
f"<패키지 클래스?함수?명>.main()"
)
# 이후 모듈 명령어 방식에 사용되는 원하는 옵션(-t, -v 등) 추가하여, subprocess.run으로 실행 가능
# 그 밖의 원하는 로직 실행